package com.qingcheng.service.impl;

import com.qingcheng.dao.TbBrandMapper;
import com.qingcheng.dao.TbSpecMapper;
import com.qingcheng.service.SkuSearchService;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.swing.text.Highlighter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName SkuSearchServiceImpl
 * @Description
 * @Author Simon
 * @Date 2020/8/20 10:10
 * @Version 1.0
 **/
@Service
public class SkuSearchServiceImpl implements SkuSearchService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Resource
    private TbBrandMapper tbBrandMapper;

    @Resource
    private TbSpecMapper tbSpecMapper;

    @Resource
    private RedisTemplate goodsRedisTemplate;

    /**
     * 匹配查询
     * @param searMap
     * @return
     * @throws IOException
     */
    @Override
    public Map search(Map searMap) throws IOException {
        // 封装
       // 查询请求对象
        SearchRequest searchRequest = new SearchRequest("sku15");
        searchRequest.types("doc"); // 设置类型

        // 查询源构造器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 布尔查询构造器
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        /**
         * 关键字搜索
         */
        MatchAllQueryBuilder matchAllQueryBuilder = null;
        MatchQueryBuilder matchQueryBuilder = null;
        if (StringUtils.isEmpty(searMap.get("keywords"))){
            matchAllQueryBuilder = QueryBuilders.matchAllQuery();
        } else {
            matchQueryBuilder = QueryBuilders.matchQuery("name", searMap.get("keywords"));
        }


        // 商品分类的过滤
        if (searMap.get("category") != null) {
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("categoryName", searMap.get("category"));
            boolQueryBuilder.filter(termQueryBuilder);
        }
        // 品牌过滤
        if (searMap.get("brand") != null) {
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName", searMap.get("brand"));
            boolQueryBuilder.filter(termQueryBuilder);
        }
        // 规格过滤
        for(Object key : searMap.keySet()) {
            String s = key.toString();
            /**
             * startsWith判断字符串是否以指定前缀开始
             */
            if(s.startsWith("spec.")) {
                // term精确查询需要设置属性的类型为： keyword
                TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(s + ".keyword", searMap.get(key));
                boolQueryBuilder.filter(termQueryBuilder);
            }
        }
        // 价格过滤
        if(searMap.get("price") != null) {
            String[] prices = (searMap.get("price")).toString().split("-");
            if (!prices[0].equals("0")) { //最低价格不等于零
                RangeQueryBuilder price = QueryBuilders.rangeQuery("price").gte(prices[0] + "00");
                boolQueryBuilder.filter(price);
            }
            if (!prices[1].equals("*")) { // 如果价格有上限
                // 小于等于
                RangeQueryBuilder price = QueryBuilders.rangeQuery("price").lte(prices[1] + "00");
                boolQueryBuilder.filter(price);
            }
        }

        /**
         * 分页
          */
        int pageNo = Integer.parseInt(searMap.get("pageNo").toString()); // 页号
        Integer pageSize = 30; // 页大小
        int fromIndex = (pageNo-1)*pageSize; // 开始索引
        searchSourceBuilder.from(fromIndex);
        searchSourceBuilder.size(pageSize);

        // 排序
        String sort = searMap.get("sort").toString();
        String sortOrder = searMap.get("sortOrder").toString();
        if(!"".equals(sort)){
            // 根据什么去排序
            searchSourceBuilder.sort(sort, SortOrder.valueOf(sortOrder));
        }

        // 高亮设置
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name").preTags("<font style='color:red'>").postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);

        // 如果map中传过来的keywords为空则查询所有的数据
        if (StringUtils.isEmpty(searMap.get("keywords"))) {
            boolQueryBuilder.must(matchAllQueryBuilder);
        } else {
            boolQueryBuilder.must(matchQueryBuilder);
        }
        searchSourceBuilder.query(boolQueryBuilder);
        searchRequest.source(searchSourceBuilder);

        /**
         * 多词条分组(根据商品分类)
         */
        // 多词条分组
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("sku_category").field("categoryName");
        searchSourceBuilder.aggregation(termsAggregationBuilder);

        // 封装返回结果
        Map result = new HashMap();
        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits searchHits  = search.getHits();

        long totalHits = searchHits .getTotalHits(); // 查询出来的记录数
        System.out.println("记录数"+totalHits);
        SearchHit[] hits = searchHits .getHits(); // 查询出来的数据
        // 商品列表
        List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
        for(SearchHit hit:hits){
            String id = hit.getId();
            Map<String, Object> skuMap = hit.getSourceAsMap();
            skuMap.put("id",id);
            // name高亮处理
            Map<String, HighlightField> highlightFields =
                    hit.getHighlightFields();
            HighlightField name = highlightFields.get("name");
            if ( name!= null ) {
                // 如果传过来的keywords_name不等于空
                Text[] fragments = name.fragments();
                skuMap.put("name", fragments[0].toString()); // 替换原来得内容
                resultList.add(skuMap);
            } else {
                resultList.add(skuMap);
            }
        }
        /**
         * input搜索框的返回结果
          */
        result.put("rows",resultList);

        // 拿到结果集当中的Aggregation对象
        Aggregations aggregations = search.getAggregations();
        // 拿到Aggregation对象下的对象格式为key： val
        Map<String, Aggregation> asMap = aggregations.getAsMap();
        // 拿到分组对象
        Terms aggregation = (Terms) asMap.get("sku_category");
        // 查询到的结果集
        List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
        // 返回商品分类的结果集
        List<String> categoryList = new ArrayList<>();
        for (Terms.Bucket bucket:
                buckets ){
            categoryList.add(bucket.getKeyAsString());
        }
        /**
         * 分类列表的展示
         */
        result.put("categoryList",categoryList);

        /**
         * 品牌列表的展示,只有当用户还没有点击品牌的还是采取查询
         */
        String categoryName = ""; // 分类名称
        if (searMap.get("category") == null) { // 如果没有分类条件
            if (categoryList.size() > 0) {
                categoryName = categoryList.get(0); //提取分类列表的第一个分类
            }
        } else {
            categoryName = (String) searMap.get("category"); // 取出参数的分类
        }
        if(searMap.get("brand") == null) {
            // List<Map> listByCategoryName = tbBrandMapper.findListByCategoryName(categoryName);
            queryCategory(categoryName);
            List<Map> listByCategoryName =  (List<Map>)goodsRedisTemplate.boundValueOps(categoryName).get();
            /**
             * 返回品牌列表
             */
            result.put("brandList", listByCategoryName);
        }
        /**
         * 规格列表
         * 拼接字符，区分存储的redis缓存
          */
        queryOptions(categoryName+"_spec");
        // List<Map> listByCategoryName = tbSpecMapper.findListByCategoryName(categoryName);
        List<Map> listByCategoryName =  (List<Map>)goodsRedisTemplate.boundValueOps(categoryName+"_spec").get();
        for (Map spec: listByCategoryName){
            String[] options = ((String)spec.get("options")).split(","); // 分割字符
            spec.put("options", options);
        }
        /**
         * 返回规格列表
         */
        result.put("specList",listByCategoryName);

        // 返回总页数
        long hitsTotalHits = searchHits.getTotalHits(); // 总记录数
        long pageCount = (hitsTotalHits%pageSize == 0)?hitsTotalHits/pageSize:hitsTotalHits/pageSize+1;
        result.put("totalPages",pageCount);
        return result;
    }

    /**
     * 缓存穿透：缓存和数据库数据库都不存在的数据，而用户还在不停的对这个节点访问，对数据库形成了极大的压力，这就造成了缓存穿透（本该访问缓存的但最终访问了数据库）
     * 缓存击穿：缓存击穿表示数据库中存在数据，但在缓存中获取不到数据了，引起一瞬间的压力过大（大部分原因是因为缓存设置了过期时间）
     * @param categoryName
     */
    public void queryCategory(String categoryName){
        if (goodsRedisTemplate.boundValueOps(categoryName).get() == null) {
            List<Map> listByCategoryName = tbBrandMapper.findListByCategoryName(categoryName);
            if ( listByCategoryName != null ) {
                goodsRedisTemplate.boundValueOps(categoryName).set(listByCategoryName);
                // 过期时间为23个小时
                goodsRedisTemplate.boundValueOps(categoryName).expire(23, TimeUnit.HOURS);
                // 设置过期时间会造成缓存击穿，设置为永久数据/缓存预热
            } else { // 解决缓存击穿
                HashMap<Object, Object> map = new HashMap<>();
                map.put("simon","duncan");
                listByCategoryName.add(map);
                goodsRedisTemplate.boundValueOps(categoryName).set(listByCategoryName);
            }
        }
    }

    public void queryOptions(String cName){
        if (goodsRedisTemplate.boundValueOps(cName).get() == null) {
            String[] str = cName.split("_");
            List<Map> listByCategoryName = tbSpecMapper.findListByCategoryName(str[0]);
            if ( listByCategoryName!= null ) {
                goodsRedisTemplate.boundValueOps(cName).set(listByCategoryName);
                goodsRedisTemplate.boundValueOps(cName).expire(23, TimeUnit.HOURS);
                // 设置过期时间会造成缓存击穿，设置为永久数据/缓存预热
            }else { // 解决缓存穿透
                HashMap<Object, Object> map = new HashMap<>();
                map.put("hang","dali");
                listByCategoryName.add(map);
                goodsRedisTemplate.boundValueOps(cName).set(listByCategoryName);
            }
        }
    }

}
